Skip to content

Add instrumentation for chat.completions.parse() (structured outputs)#18

Open
Nik-Reddy wants to merge 1 commit into
open-telemetry:mainfrom
Nik-Reddy:feat/structured-outputs
Open

Add instrumentation for chat.completions.parse() (structured outputs)#18
Nik-Reddy wants to merge 1 commit into
open-telemetry:mainfrom
Nik-Reddy:feat/structured-outputs

Conversation

@Nik-Reddy
Copy link
Copy Markdown

@Nik-Reddy Nik-Reddy commented May 15, 2026

Closes #12

Background

Originally tracked in opentelemetry-python-contrib#3449 with a previous implementation in opentelemetry-python-contrib#4416, which was approved by @lmolkova and @lzchen. That PR was closed when the GenAI instrumentations moved to this repository. This PR ports the same approach to the new repo, aligned with its current structure.

Changes

Adds telemetry coverage for OpenAI's chat.completions.parse() method, which powers structured outputs. The parse() method was introduced in openai SDK 1.40.0 and returns a ParsedChatCompletion that extends ChatCompletion with a typed .parsed field on each choice message.

Instrumentation (__init__.py)

  • Added _is_parse_supported() version guard that checks for hasattr(Completions, "parse"). On openai < 1.40.0 the wrapping is skipped entirely (the repo pins openai >= 1.26.0).
  • Completions.parse and AsyncCompletions.parse are wrapped with the same telemetry functions used for create(). Since ParsedChatCompletion inherits from ChatCompletion, the existing wrapper logic (span attributes, metrics, content capture) handles it without changes.
  • Corresponding unwrap() calls added to _uninstrument().

response_format type handling (utils.py)

When parse() is called with response_format=SomePydanticModel, the SDK sends a bare Python type rather than a dict or string. Both attribute extraction paths now handle this:

  • Legacy path (get_llm_request_attributes): isinstance(response_format, type) branch sets gen_ai.openai.request.response_format = "json_schema" via the GenAiOpenaiRequestResponseFormatValues.JSON_SCHEMA semconv constant.
  • Experimental path (create_chat_invocation): same check sets gen_ai.output.type = "json" via the GenAiOutputTypeValues.JSON semconv constant.

Tests

  • test_structured_outputs.py / test_async_structured_outputs.py: sync and async tests covering both content-capture and no-content modes.
  • Tests verify the wrapper preserves response.choices[0].message.parsed (the structured output field).
  • Tests assert the correct gen_ai.output.type / gen_ai.openai.request.response_format attribute based on semconv mode.
  • All tests use VCR cassettes and are skipped on openai < 1.40.0 via pytestmark.

CHANGELOG

Adds an entry under ## Unreleased referencing this PR.

Acceptance criteria (from #12)

  • Sync and async parse calls produce spans with the same attributes as create() calls
  • gen_ai.output.type is set to json when response_format is a Pydantic model class
  • No telemetry regression for existing create() calls
  • Tests pass on both old and new semconv paths

What's not changed

  • No changes to the stream wrappers, metrics, or util layer. Structured outputs via parse() are non-streaming only.
  • No changes to the Responses API path.

Copilot AI review requested due to automatic review settings May 15, 2026 06:36
@Nik-Reddy Nik-Reddy requested a review from a team as a code owner May 15, 2026 06:36
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds OpenAI structured outputs telemetry coverage by wrapping chat.completions.parse() and handling Pydantic model response_format values in request attribute extraction.

Changes:

  • Adds parse wrapping/unwrapping for sync and async chat completions when supported.
  • Maps Python type response formats to JSON/JSON schema telemetry attributes.
  • Adds sync/async structured-output tests and VCR cassettes for content/no-content modes.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
instrumentation/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/__init__.py Adds parse support detection and wrappers.
instrumentation/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/utils.py Handles type-based response_format values.
instrumentation/opentelemetry-instrumentation-openai-v2/tests/test_structured_outputs.py Adds sync structured-output tests.
instrumentation/opentelemetry-instrumentation-openai-v2/tests/test_async_structured_outputs.py Adds async structured-output tests.
instrumentation/opentelemetry-instrumentation-openai-v2/tests/structured_outputs_utils.py Adds shared structured-output test model and prompts.
instrumentation/opentelemetry-instrumentation-openai-v2/tests/cassettes/test_structured_output_with_content.yaml Adds sync content-mode cassette.
instrumentation/opentelemetry-instrumentation-openai-v2/tests/cassettes/test_structured_output_no_content.yaml Adds sync no-content cassette.
instrumentation/opentelemetry-instrumentation-openai-v2/tests/cassettes/test_async_structured_output_with_content.yaml Adds async content-mode cassette.
instrumentation/opentelemetry-instrumentation-openai-v2/tests/cassettes/test_async_structured_output_no_content.yaml Adds async no-content cassette.

Comment on lines +204 to +208
self._parse_supported = _is_parse_supported()
if self._parse_supported:
wrap_function_wrapper(
"openai.resources.chat.completions",
"Completions.parse",
Comment on lines +33 to +34
def test_structured_output_with_content(
span_exporter, log_exporter, openai_client, instrument_with_content, vcr
Comment on lines +33 to +34
@pytest.mark.asyncio()
async def test_async_structured_output_with_content(
Wrap Completions.parse and AsyncCompletions.parse with the same
telemetry wrappers used for create(). ParsedChatCompletion extends
ChatCompletion so the existing wrapper logic handles it correctly.

A version guard (_is_parse_supported) skips wrapping on openai < 1.40.0
where parse() does not exist.

response_format handling in both the legacy and experimental paths now
recognises a bare Python type (Pydantic model class) and maps it to the
appropriate output type attribute (json on experimental, json_schema on
legacy).

Includes sync and async tests with VCR cassettes, skipped on older SDK
versions.
@Nik-Reddy Nik-Reddy force-pushed the feat/structured-outputs branch from 8e8b16b to 489758a Compare May 15, 2026 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Instrument OpenAI structured outputs (chat completions parse)

2 participants